{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "1793457b",
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "import h5py\n",
    "from lr_utils import load_dataset "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "a2332473",
   "metadata": {},
   "outputs": [],
   "source": [
    "train_set_x_orig , train_set_y , test_set_x_orig , test_set_y , classes = load_dataset() #调用lr_utils的load_dataset()函数"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "8bcafe4b",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<matplotlib.image.AxesImage at 0x18092143d90>"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAPsAAAD7CAYAAACscuKmAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjQuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8rg+JYAAAACXBIWXMAAAsTAAALEwEAmpwYAAA/QElEQVR4nO19a5Bl13XWt+67b79uP2Z6ZjSSRpYU27Icy45wZFtQfsTBhBD/CYHwKAOqUkEFKhShsA1VFFBQlfwh5AcVSoUDLghxAsHY5SIQI2zCw5EtP6OHLY1GrZmeR/dMv/u+z72bH337rm+t2/fOnZme20ru/qqmZp/e++yzzz5n37PWXmt9S0IIiIiI+KOP1HEPICIiYjSIiz0iYkwQF3tExJggLvaIiDFBXOwREWOCuNgjIsYEd7TYReRjIvIDETkvIp86qkFFREQcPeR27ewikgbwCoCPAlgB8A0APxtCeOnohhcREXFUyNzBue8FcD6EcAEARORzAD4OoO9iT6UkpFJy047974891vNTqbRpl05zOWvq2u3WoeUQ2u5aejERO9Z0ZrpbbiYF6s//YDapw8TUpFItKts6gY7F/ggH1244mLP8pMpwvfA4ej4L4dBiz/VSqf4CpOnfjTFNDzSby3fLjXrNtONXKp22rzSfV5ic1XJx0rQr5LXd9vp1U7e5qcf87gxCz+xK/1p+f+x8DHWpHoQQDn24d7LY7wFwiY5XAPzooBNSKcHUVKZbttDjRsPeZZJoXTvkuuXi1IxpNzejL9VM6ZSpq5Q3u+VaZVuvVa+6a+kCTGdypm528clu+crmO7S/atO0Q3KlW5Rww1RNFLa65akJW5dOVbrlVsJ92h+kVO/SImhds0U/Hu4Hiee/51lQ00aiLzf3BwD83vsfPH5pCwWaR3epRl3nu5XY/kslfb6n731Lt3zx9e+bdoWUXmu+tGDqTt33Q93yW9/7sW757e95wrR78NxD3fLv/Pt/aeo+/9v/qlveK2+hH1L0A+o/FOYHz9VVa/qsa1Waj5afUz46/Md6kKR+J4t9KIjI0wCe3i/f7atFRET0w50s9ssA7qXjs52/GYQQngHwDABkMqlwsOB7vuuiv3wZ96UJJK6HtorPjWTCtMvltI+TS2dMXTOc7pavLr/QLbcT+1UWFuvbtm5n41t0rbM0Dvs1abdVRAxhx9SFgUK4HFqEk8r40H/l+Yedyy33i2++Gk4yZSkgoa95ktg++omf+31ouRoa3XI6bUX6pKkXLxQKpu6eex/olnd3VDILLav+sEg+NTtn6hZO3a/jpTG2nCQibZYqrITRbKra4J/esJI2f+lDz1dvNF/BO9mN/waAh0XkARHJAfjzAL54NMOKiIg4atz2lz2EkIjI3wTw3wGkAfxaCOHFIxtZRETEkeKOdPYQwn8F8F+PaCwRERF3EXd9g64XB/qJ1SBYTfc6e1tUZ2+JDrnVtjpe0lL9b37B7sYv3P926l81rZXXXjDtqmXdqU+adVNXb6remJGvdMuLpR8z7W6sq44aEm96I9Oe09XsDi7Pj9UhB+NwPdrv7LZ5p971wLp4K+E++pspB6HdIL3f6av5gu673P/Aw6YuTW03N9b0nIx9dwpkXstkrMk1P1HslnMZHW8mKZt2lc2r3fKNtRVT16L3qtekRro4/7mnmfStNfssvn/bC591aP+DHkl0l42IGBPExR4RMSYYuRjfde4ZYG1IiXMAERUDBSoWt1sV067VnuqWJyesWe4EmeIqD76rW67tWdPY1upyt1ytOPGZ7EnVuvoTTRS/ZpotltRkVKtbL78M9LjXApM6tM476LFZy/tKsWjN4njbifFsUutx3uD+B3i4DYL0EW8zWfvKnblP5+rkyXtM3euv/YEekBm06J7tVFE9G+dP3mvqEnLUubz8Srf88INWZdi9rqL76uolU9cO5HGJ/uB7TrmHa1W2AS9/6C+q83Gvz8rNn038skdEjAniYo+IGBPExR4RMSYYqc4eAtDuuCWyeywApMA6njMTUeSYcTclN0wAqDe0z3bT1s3PaMRT+5zqa7XdDXuthppkUs6PNE/BDNJQHbK8/YppN7egdQ/e/zZTV6mpzr69sWfqgtGjtY928HsHrEf74BTug8ptb3rTcsu5wbJqLtLfoMR6o9cYWX9lF9nFE0um3YP3a4DL6qo1ee1s67MpkK5fzNkApUJRzWvzJ6zeX6PAqakpdau9dv57pt2rm9e65StX3jB1vPfhzaX94N2i7Vz5b+wgPf3oEL/sERFjgrjYIyLGBKMV4xGQdMw8Too3wkvbeYwlfcw/krbeaVWKoCpXrEltcVZjo4sTKkpvXrdi3+72g91yPm899Cpb6sWVoTj4azdsXPrm+nK3XJq1ZqKTSxotl0ktmrpmXT3BWpt6L+zBBTiTmhPP2dSUtPqbzYxwPsCMwwQS4jwbU1zXx6MLAEpz893yWx58q+2D+tzZXDN1bPLKZVUcF0dQgazOcSqbN1Vzk2qWO71Y0j4qVn27vKnvS9mZYwfwTvTFIA86Cf1FfEavajTMFfurAfHLHhExJoiLPSJiTDBaD7qg3lrptBcrmXrKnsakA7wzn3K78a1EReum8wrLpVW8rbWVjCCpbJp2i2eU7GCtZSmr6tXdbnmCxMqpsg2q2NjT4ytXlk0di8XT07O2bkY9ABPaZQ/tddOuWtVd/JYjcmBvuEHiPouEubwNHmFpMZfX+8znrYhcJNKIpGnHkclpn6dPq+oyPW2pxK5dXu6Wg+N3S5MloE3qiee0m5pWwoq29/Kjd6JR1j5OnzpnmlWauhSS//U7po6tQzKkbN3LAteHmKQHvG3fX/XyVGLDeDfGL3tExJggLvaIiDFBXOwREWOCEZveVHds93cKO+TEPiyKwfKHJw3Vv69etZFL16+83i3PL6opaHZ62rZbXu6WvZ6bm9C21V01t804PbRCvObNuo3MW7t2sVsWuc/UnTilx4tLahL0euIEETJsbVqOc+air9M4fBRWq806sOufiB/Z5DU5OWXaLZ3SSMKGo4GeJJrvFD2ztWv2uTRquv/QDp5HX8ecoUEWJuw4Fk/pnkCtavdZNtd0vtNnlHT00Xd/0LR76fua7iBJ7F6QHZSPZrt78NsBt8sjf4D4ZY+IGBPExR4RMSYYuemta1EaJJN4UYl+kthrK5X25gfljLu4smzqLr6mYlppUrO5TM9ZL7bk1Ze75cmZk6ZurnSiW76+QtxsdSuasrfe6qb1xmo0VKzf3nYZYYhLrTipfZwgMRUAalXto+HUhCqJsY2GiqO5nH3UtbrWeVMWW3WyJMZPz5RMu8VFDWpJ5a0qs72tKtUykVB477T7z+i97e5aM2iWny95zaUm7LUaTLbRtryBp86oavSBj/4Zve79VoX6jX+nal5zkBjv+eOGtKgZDrqedz/0Kfs+BlwgetBFREQcIC72iIgxQVzsERFjgmPgjd/XKXrTMg/IgJkh0kbikIdL2czqys6u1Q1f/L7ywy8tqemtvGn1xAwRVtRrllyidFpdaU+SmWh3Y9W0y6WVeGJ2qmjq9mqqDzZd6uG9HeWsZ7PZPfc+ZNpNFtXNtrpjo7eSpmaQTZPO690pC3kmgLDzncup6S1Lcz8/f8K0u/c+5eJPHF97uaJzV2Z3Yu/NSibAtovuy1Gf6ZS+qpmMfe7sdtxOrM7+yDvf0y0/+f4f6Za/981vmnYXL76mQ3RmYfs+OrfjPpq6f4ett+xw39jbzLLdFze9qoj8moisicgL9Ld5EfmyiLza+X9uUB8RERHHj2F+Yv4tgI+5v30KwLMhhIcBPNs5joiIeBPjpmJ8COH3ROSc+/PHAXywU/4sgK8C+OQwFzwQTdo+1fAATmwh01AYwFXHh0nLmk9efkVNbz/86Du75auv2/RP0lQRfCJvUzGnyQyVny51y9mpkmlXvqFiqzeEcB8px+Cxs7NO7VSUPnHGTshJMnnVKrumbmtL+ygUdA4KOUuisbun53mOOyMm0w1kHDHEAnn8lWs28q/VUBNgljjjWi6lVo1SZjedx2KTVLuEcwe4ccwQv2BoWy+8+QV9hls3trrl//esTVF4/bqmf+plnuh7YJsNkLM5TXOv6H/r8nnvte5e1NtSCOFgdq4BWBrUOCIi4vhxxxt0IYQg0hPl24WIPA3g6Tu9TkRExJ3hdhf7qoicDiFcFZHTANb6NQwhPAPgGQAQkRD67cZTOdUjcBCRA5M6tPxvDItKtm5zU3et9yoq3t7/tidMu+tXLnTLdUdH3SKii4REzpn506bd9oYGp7Rbdsc9n6Xdeee5Vt7Z6panSyqqzhAFMgBMEHXyySXLodeo6I7+xYt6L55Pr17TcbWC3QUHHS/Mqxfh2XvfYpuR5WJrw74CdeLoY5KL7U07Hxsb6kXYduJsmrzmshT8MkUqFACsXKQgp3mreu3Rs/i9r/6unnPNWlA46Omod8EBQALRnA+QuIfP4nor5+3jdsX4LwL4RKf8CQBfuM1+IiIiRoRhTG+/AeBrAN4qIisi8hSAXwTwURF5FcCPdY4jIiLexBhmN/5n+1R95IjHEhERcRdxDB50faJzOOWQkzeElSjihk8NMNF5PatJJp9LlzVK7bHHnzTtZhaVkOHVl79l6jbXVc/LTJS65VNnrS57g1L+Vms2Ki2h1MMp2DRGLGglFLG2ds2mI1o6pWmJJx1xBtdt7251y63Eeafl/bUVc7Oq9y5QuqbSnPWd4gi+leXvm7pN0uGrFTXL9ZiM6NFOTloCziLp5vMljU5cOn3OtJOgc3r2pO1jknILfPVr/6dbXjz3TtNuYvL/dct75S03RN4LGhI99mMmXfHRmn07se0GpNsaBtE3PiJiTBAXe0TEmGD0Ynwf+SMMkOOZpCIzQJbh09KekIGOX3lVyRSuXXzctHvvh36qWy7MWJHw6/9bTTdt4muvOC+2GSLEaDSsxxh7rnlucSaHyBCRRaViVYE6mdeKBesZl8xpsMpEUc1VTSfGz7LXnNjXYGJWxfVT92g6rILjoHv9tR90y2uO849JKtrk1ZZxATP5oqohMyVrNsvQcy9OqvkuJdbjr1jQuTq9ZAlHlpbULHrv/Zp66o3r1lTYZDNrD7eEHFbsganqkc0HmM0Mr6Kp6TuOAdpQX8Qve0TEmCAu9oiIMUFc7BERY4KR6+yqa/RXOoIjlWf9hCOyelLfkl7eq7Nr261NjQw7TwSTAPChP/mnu+UH3/KAqbv4yqlu+Y2Ly90ykz0AQHFKdd6pptOmsqpvJw3Lcd5q6X1nMtpnweVYW7+hJsDGlNWjZ+dU773vfiWX2Nqw/PJNOi+07HzPn9D7nFtUnTdXLJl2lbLuJTRdH6yHZohkJJ22Jr8Z2qcQl7duc2urW04Racns7LZpt3i/koqUFqz78MySEn+cPquEGv/3a//GtKuTibSXWEXLAzgphvo7MLzZzI/Dm5pvtdP4ZY+IGBPExR4RMSYYvemtK4pYuaPF6Xp7oo768dP1l7fabS9W6nmc8viNSxdMu+8899VuubRko9kms9p/zvCg2XHMkAdawXmFbRGfertuOe7SZJba2aH00BOWxy5PKZa9qDcxoSaqtz367m75xe9azrXKrs7PVMly58+T6J4uaH/lmjUjtikaMTthI/MyZI7kCDjPc58iwg5xIn6tqp535bKa8nbKdt7KVR3X1WtXTV0mp+O6TPz1lR3L2V93fIAMw+o+IFpz2Og4zwfYN92y13TZ6uyiygeZBA8Qv+wREWOCuNgjIsYEo9+N7/zv6Xo5UMOLJP3FeN/uVkcBpFJ2Cl6/eLlbnrh80dRl2yqC5ilbas3tRKczKnZPu530KcpuurVtaaDTaRXPp+Z0R3zHUVUXJnW3P5N1om9Dg0JOLmm7mWmrCtQret+LLsAlW9A+A1kMGo5nTkj1KmStZ9wWlVml8upVmUTyTMbeCxOVlCmoZ5t49gCg3lSryYkz50xdq6Y790xMsnr9mmnXJC68Hoa4QdvxPF5zkrMUGQ664fbj+9FU71/MZ5MNvWNwiF/2iIgxQVzsERFjgrjYIyLGBCPX2Q/Un+BY95g70qnzkBRzyg+rmHvzBpXpAk6FxOSsRo3trb5q6h64R01SIaceaC9dsOQS5bJGUGUydoqnKbJrasbqynUybTVrzD1v75nJLksujfIe7QNsbKnX3AKRWgDADqWampqzJkaOpGM+9ZozT21s6bXKezbyr15TXT9JdH+j5VI8pQbo8znaj+A+qs70lpDnXco999fPK6nG9ygF2E7Z7j8w/Hybup5AtMOJLW4vKbM/x6eaGtT47vHGR0RE/CFDXOwREWOC0YvxcjhvPJsZ2gPclG6XwjsY7zr9+6UrK6bd6+fVy+r0gvUs+6H3fKhbXice+tXdpml35aUX9cAF5OyQmD1TKpm6Vlvb7m6rh9f2pvX2miJSjVP3nDV1gX6/96iPcw++1bTjjKn3PGCzxFaJvz6zqa/I1QuvmXY7JMbvkloAAEli56Q7PvdsWXSfKFoijjyleeIUVe2W7TsQB936up2ra+vqsVitq7ifL9jgpVqVAmHajkd/eNc4PcVVDTSjcRfmHNfHgEzHPanQDkH8skdEjAniYo+IGBPExR4RMSY4BndZOSgMD8PHdztmOD8I7WN1zbpNrq1e6ZZbLkLr4iU1sTGJot9jEGIZqFatSarBpqamJa/IFNSllXX7piOtvLz8Src8OTlt+yCSxq21rW75oR961LSbnVc++Iwzdu5sKhnj6tXlbvm6J5XcVX3Y67n8ZNJpjRD0pjcmGck5118mgWTCSTbDAcD6upoYX3ntvKm7skYuyVndE5CUfS7B+G/3N9sOTpXcP18c75G0nXt1vz2pQVr4MDq6xzDpn+4Vka+IyEsi8qKI/Hzn7/Mi8mURebXz/9zN+oqIiDg+DCPGJwB+IYTwCIAnAPyciDwC4FMAng0hPAzg2c5xRETEmxTD5Hq7CuBqp7wrIi8DuAfAxwF8sNPsswC+CuCTN+vvQAzy4pARX4bwBjqs3bBiPbfaI35zADhP5qXKok2t9PWvfL5bfvRdP9otnzptzV+vLWsK4arrP03RYT3SHKW24ntJnIjcIPH5pe/+vqk7c0Y95Sp07UtvWJKOGSKoqDatWFyuqnqxua4i/R5FngFAnVJU9ZiJaPwsqs+4dMtp8jBMO2/DQKmjU5S+OZe3Jrryrpr9btywXHtJW8cxXVLvyGrDmu84tZf3oBv4XhlOeTbvumeW6FwlTW9+7Pe+D5+iWclZ+q+dW9qgE5FzAN4N4DkAS50fAgC4BmCp33kRERHHj6E36ERkCsBvA/jbIYQd/rULIQQROfQnRUSeBvD0nQ40IiLizjDUl11Esthf6L8eQvjPnT+visjpTv1pAGuHnRtCeCaE8HgI4fHD6iMiIkaDm37ZZf8T/hkAL4cQ/jlVfRHAJwD8Yuf/L9z0akKeh72+gFT0KW1vJ0HtcPAmo2vXrlCdNXlNkZlrjkx0ScEaInJ5dcUsTlnT2AT1sXH9iqmbotxsc4vKVNNyrqeSU72/UrHRW5vEasNRai9+9znT7l0/8v5uuT09b+qaRMjJZJG1ph0HR5v58ME8kWRmyBQ561yEi9PqktxyfXC+O86Z53V2iD7DZmIj8/j5psgE6N15hRIF9hBCsknNXrlvarbE6eGBzIVJ0p9jn8220kNM2e8AGMaWPYwY/wEAfxnAH4jIdzp/+/vYX+S/JSJPAXgDwM8M0VdERMQxYZjd+P+D/j8bHzna4URERNwtjNSDTtDfjGE2/G7DO+iowN5wCdKmbqOiot/KDfXAmpm3KZgWFtTE40kU2yRmLi3a9MJ7FRWZCyQGTxYtWWSWPM1ajrmTI+TYhHTp0uum3anTmibpwbeXbB8UzcapsprOc41NRj5lV5ZE8BRHHLoUTy06PnXP/aZuYkaJPhJSSXJ5+9quXtXIxVarYeoyJPKzJ9+k47nPUgqvem0QsUX/Y34ULWdXbZs6Z3ozHnRD+tD1BIbGqLeIiIgO4mKPiBgTHEMW1wMSuv4edF7Sv4ub8YeIQ4pM1u76ZvMq+m3uqqhXmLWi6R55dMHt9henlXiivGW9vZZOqWjNZAppl76zVlHPuIILHqmQV1uTgi88f9zy6xowMjNv/aE4GKhc0XG0nAjeoECVrBsj7z5n8jrGquN+qzVUvs3l7Ot4MqOqwOLSGb1W2qpX+ayqOUz6AQDgcRAvv8/yO5DLfRCjhOE2DIeWAf8OD3jp+FKD1oFPrhA56CIiIg4QF3tExJggLvaIiDHBsZFX9A302W90Wxjkade/zl6MzTOLC9Y0VqSIrYSimKYmreltb5uIEluWoIIVtPmlc6ZmYkr7aa9qzrmkaT35muTVlkrbHGvsGVar6xhTLqJsfUN12xe/97yp29rVPQHOgeY9vzgawn812POOy2mnoLYbuq+wu2f1+VnaE6jQ3oHPOZchYspKxc735LR6LOayOgeecDJHpsJ6zZKWmHfEvbhty6xCZfde0QSl3H5Bk/Z1eshWjxDxyx4RMSaIiz0iYkwwcjH+TjFk9tzh4S0YVPapm1JMTkBi2hbxrAOAENHCwqkzpm6Tgl8Wlu4zdRmS9cp1Fd1rDesVtktidsEFhVh+fBW7OSU2YE1qm+QxBzhPOVG1Rpz4GVoc5mxF/IREcBZNcxlrNmtDr9UrwGqfDTIdlh1H/cSkkozMzJ8wdS1KOZ1Oq0qSzdl7Mc/avViGiKKHMIWqSMRPuReLU4P33CcF8vA09qqed/bCxy97RMSYIC72iIgxQVzsERFjgtHr7EOpHf19Em9fT+8TTSRet9LjzfWrpm5nW91bz5zV/GjFoo2gYn141xFOcsRaOmX11+ukzzfJ3JaCIy9sqP7aQ7TASh/tMaTcfTIZhOdyZ8XRRCO6yDZ2n20G+90o5MiNlyLRvJtqJq8mr9KcJQHJkn5forx78wtWL9/eUJKkpjNTZqiP+TkiH5m0ev/yBeZ8d+a1Vv+6VOpw05s3LTMRxbBWZ098eackLvHLHhExJoiLPSJiTHAMprcDUcSLKMMF7Ru+rluS6Q/vM5OxfeRJ/Mw4MxGLvmwmq1etx5X5BXVEBQvE1z41bXnpV1aWu+WNGySa1q1omqb7rtatWc6YbjgKKzjSCBJ3E8ctlyFu+zTNgZ9vFsm951eLri00I01nAixO6rXKuzYl0x6ZNLeuauqtR979ftMuX9BnlhPrGZci02F1V9Wf1etO/eF5c1pNK2G1b7jouB5VwN13v2sbHIVpmRC/7BERY4K42CMixgTHQF7RRzYJ/Xc8hzr/iMDiud8tn6DgCfZOq7nAiVaiXlvb65ZMoUh9pPNbpi5NojZrF4FIHAAgy552NUtKwV5uPFcp/7NOddmcJcBg6wJnkK2U90w73o1v1KyqEcDBLzpXxbwP3NHzVi6/YuoyJMc++KDuxp+Zt9TX6+RRt75trR/luo651dLnubVlx1ur6Xh96iajDrnXz3jQ8Tvctg35femxNQ27yT5sitc+iF/2iIgxQVzsERFjgrjYIyLGBMdgejs8tawn6LtTDPQ2IkVLnAbF0U+VitVRmdiCT6uVrclod2+rWy47DzohEsuC4y6fIG+ypMFeeNbbK22i75zJi1MRk+4pPb/rel7ekVZOEmd9jbwBvS6bkDmph0+9pePI5XTe0s7UmSIzX4DV58+c0GfxN57SVIGrKZsiO31FST/KDdt/mcgsTHrutjVFZswcuOg+9LONOZMdvRS9rzOnN7M1w6cav7PcCjf9sotIQUS+LiLfFZEXReQfd/7+gIg8JyLnReQ3RSR3s74iIiKOD8OI8XUAHw4hvAvAYwA+JiJPAPglAL8cQngIwCaAp+7aKCMiIu4Yw+R6CwAO5Nls518A8GEAf6Hz988C+EcAfnWI/vb/91xk7BnX55ze2uFFGTaZiCnbqzXJmyxpebFV68q7W92y5zObm1cz0f33P2TqJKXi4xuvfMfUrW9qoE2bzHCTk5agorGrZiPx6ZSaOmYef9sRcWTzOneFvB3/bElNW6yGBDcfnOIo7T4bk3kV3fnZVl0wTYE83lJuHGfv08CVs+96Z7d85UUrRDZoXJKxqgCndeIcTBXn9cgZaT0RB8OL4P1ezVvJfWDe70ESPQXdiDPtDRMkM2x+9nQng+sagC8DeA3AVlAfzBUA9/Q5PSIi4k2AoRZ7CKEVQngMwFkA7wXwtmEvICJPi8jzIvL8Xc3sEhERMRC3ZHoLIWwB+AqA9wEoiciBbHgWwOU+5zwTQng8hPD4XXZ+i4iIGICb6uwicgJAM4SwJSITAD6K/c25rwD4aQCfA/AJAF+4s6Gwu+ygAQ2q6l/Z74fGkyim09qQueEBoDSnpAm5gurR+YLVqZmYcnHa6qG1Gxq9FTa/bupeeUNNfes1HUcxb3VUTok2M2l11Ap5z1bII9TvP7AbbLtt77PA6ZY5ss3p24HCw6YK9lWaprTKFdpHkElLUBEyan5MZew8vuOd57rlzT2NENzcsrzx2ztq+qyWramzWVfdnIk0CwWXsjl9eKQf4NIvD5tF2ZvXbmevybvm0saTd9vtNh6weIaxs58G8FkRSWNfEvitEMKXROQlAJ8TkX8K4NsAPjNEXxEREceEYXbjvwfg3Yf8/QL29feIiIg/BBi5B92BiaBXCmGvtiEhVgRnnrUeMaevecOTLqjIlnKib2lO00Hdd073KPMTzhREEWB5Z056y6N63kPvt553P/iMRn2tUYTWXmLNa6hvdYszziMtO6GPlGdnu2rvhb3hOOILAIR4+FpkbvTeepxKesqlW56iOfmxJ9Xj7YEffty0+7XfWemWi87E+Mff/2i3/MYVNcOlcjYKsE0EFfWGJeJIaO5YC6lUrSrA78FE0T6zNr0HrcSrMloekteix9zb77Qen8dBF+jUeeKNQf1FRET8EUVc7BERY4JjTP8kfY/8zjkfpykTZ8q1S1GgSsp5UrVJnGPShVTadlIjz6p2zopzHJySkJfVVN7u7OayGkhSnLFEC0lKz1t862Om7qG36674+d9f14qUFU0n80vd8mx1zdTVaec4U1Rxt9a0qoCQ8JhxJB2BRPwsBapknOUioQfj+frecp/6WP31v/HHuuVz7/qYaXfu7edpUFZMzeYf7JbXNnRuNhyJxg3i66vXrXieIte+PL07WRf8E2j8SdnNFXvGuc+jcbYbJGX3rxp6n97s6PeI9NI5v38P8cseETEmiIs9ImJMEBd7RMSY4Ph0dq9v0x+8Hp3Nqd5YnFT9OJO1Jhj2hsvnbR17jDHPe9NzppPu5gknq0TuWCeSyVqtaNrlcmpCqjSsDpUhpe/Vy7Om7id//D3d8nOvflP7d7zxf/3PPtEtn25fMnX/4Te+3S1f29N7y6SdXk7znbiU0GxO4ueSdXo5U9aHlN0jKZ14oFv+2jf0vPX2FdPuR/7Yn+qW37hkvd9efGG5W752Y0vbrVw07crbWlcuW6KPDJkEjenQEXE0qmR+dFFv7EWYNF1E3G3FewxIb3Y73Q15XvyyR0SMCeJij4gYExxDFtd9gSPl7GbsVZQvWBF8YlK9p4oTWi4Up2DBWUttTYvMaBz0UKlaM47NxGlFthrJrc2KmnhylNIJABbmdFyrV1dMXZlIHapla9p78gPqMfYLf03nYH3Lird/6c+p9/La+W+Zunu+pl55O2+Qd1rZqivlmh7XnOi7fl2z19ZqOj/ieQM522vOqjJSXOiWVysq0r/xFXut+y78QbecL5ZM3Wsr17rllRVVV3Z3Nk27QpbNa9akViEOvUB8/mXHG5hwZlxYJBTIcxRh2tLHbLbf/3AX8EFfR0ZeERER8YcfcbFHRIwJ4mKPiBgTjFZnF+lyr/v8YukMpd11dRMF1YHZDZZ1bwDIEqliy5lWUvy7VmhzhUGLTHF7e1avaxEJpAgRFDoO8p0d1XMnp6x5rVpWN9hM1epd335Z9ctHH/mRbvl9T9i9ie++rPnjfv9/2UeYnlMX0xJxKl4l0xUAtCiSq+4iwK6sKMFGO+E01db0xjz6DeeOu0tElW3RMTKHPABcXF7W/ieta3Glrs+iQFz2Xj+tl7e0nSf6oAi51av6PMvO5ZbvxUfOHUlKA+lTBm7LfDesbs+IX/aIiDFBXOwREWOCkYrxItJNr1SYsKJp1pE8GJAHUyAGguA40WBSFDseMRK7WSzzXGQhR1zrwYqmSU3F3dU1NQtlcvZeFk6oKS6Xt4QMrTalW7aOa1hZUc7OGqUtenlq2jYkk1cSZkzV7CL9fue0/NLLL5p2TPTRdnJkncxQnMK64NSrCpF07Lk0V5cvL3fLi/eo6S2Vtn1867nf65YbwX57CqQCzROX/ezcgmk3XTzVLVedGbFFKapSaX3dfZRem6L7fNhl0jj83fG4HdF6lIhf9oiIMUFc7BERY4KRi/HZDu1yxpFLpChApOW439htickU2rDt6kQbnM1atYCDZJjkYnLKisEcMNNq2QAUdqjLkaeWuCyfG+SBlnXBOimyGEjWzsHOhu7UL198XcdUtVaB+dlSt5zP2N/rRkW9y06fVNG37eRP5o/z3oZ5GleBaLJD085HMa/tqnWrk1y+eEH7T+kc5ApW5dnYVMtC3e3oFynYaHFeabwnXLotJtvY3bLedbu7OndblLKr5Xj98vn+eUmtt6fPPnz4OT0SvTk++gQK6oEaySsiIsYecbFHRIwJ4mKPiBgTjFZnT6XIFOX42lvET96yilDS0mEWSN1puXTFHIWVddzirOtnSc9lMgwAaJBOvbpqI9YS0lnzVzUKy6dsni2pKUic1xm33dnaMnUbG6q/bm6r7lnbc1Fp66rbF9N2rk5O6/VuBCLsSOz+BnvDFYt2DqYp5TRHV9Uqdu+A01I5vhE0SCdevaoeebMnbLLfxSXllOf7B2yK7M2N1W4577jyK3tbeuCI00uLei87FZ3HbN7ulwiZWeueR58u56M1mcvdWII9x0Xg8p2b6Dz3/DAY+sveSdv8bRH5Uuf4ARF5TkTOi8hvikj/HY6IiIhjx62I8T8P4GU6/iUAvxxCeAjAJoCnjnJgERERR4uhxHgROQvgTwP4ZwD+juzLEB8G8Bc6TT4L4B8B+NWb97YvwiTt/uY1DooBbJACC0CtpjX3ZMmcl3LmDe6DueuyGSvut8hEJT186lrH5kHmmgeA2SkVnxvOC2+Cspt6kfDSGz/oltfXVaSdXzhp2rXaOuaGMz+ubeu1t5bVI0986iZSa4pTNrNqaVaPE1KvWi3ruVarq6fdTMl6+WWJWKRCQSezJRvsUjp5H43JehsCOnfT0+pNV3fPPUdekGtrNnN4i8gr9oiwwpsb+dl6MZvJJrz03G6z1yb159RUtnz2M9fdbQz7Zf8XAP4eVBNZALAVQvdNXgFwzyHnRUREvElw08UuIj8JYC2E8M2bte1z/tMi8ryIPO833iIiIkaHYcT4DwD4KRH5CQAFADMAfgVASUQyna/7WQCXDzs5hPAMgGcAIJvPvbkjBSIi/ghjmPzsnwbwaQAQkQ8C+LshhL8oIv8RwE8D+ByATwD4wk2vFgKSA3KIAcveWav6mxm8/kRlT15B6h8y5Orq0xBzl2k3kFRKpytPBJZNx+teoei4memSqatSpNjqNfv72GD+dmNWtGO8516NIltYsHp0ZYfIMQoambe1awkquMe5xVOmbmZG9WN2Hy4WLKnk+nXNsbZTtvsW73ybcuCXd9XktXjyjGk3TRFs3qW3TOQhvMfAzwEANm7Qfa673Hdkctwid+Ss+NTOOvdtH005AEZPpzzhbfdypuje2n6/YETRcnfiVPNJ7G/Wnce+Dv+ZoxlSRETE3cAtOdWEEL4K4Kud8gUA7z36IUVERNwNjNSDLoSgJisnuXCqJS9GNcjEkx7kzRTYm8mavPg85plru+i7HJFoMF89ADTrh3OL1+vW42rjhnp7NV1qpdKcenS1Glb0bZHIzCmFp5xpLEMmKk+mkM3rmGcXdU7LL3zbtJskTrdpp2osLt3bLecLOo6rl2zaJfZgbLg0WgmZx2ZKes9wzzZNcvCC499vEl97jXj6gzNnXrigJss99qaDVY3aNN+FrH136pTWqdf01t9bjdtaTzvXzuR97h85dwsaxC0j+sZHRIwJ4mKPiBgTjDz90wHRQ68HnR6L+wlKSGxrkjzuqY0NvXPW3hpTUKdJjApuHCHoeZ7YokoeWCazqk9lRTvHuy54JFtUMTvj+OmKxLm2QF5zU7PW64zNFW3/e033WSHyBy+IVivq1Xb9hrUK3PeA7vZPzagK0UxeM+32yipal0olU7dQ0nvJFbWPjCPsyBI5RmXHzhV7oa2tafbX7c11027tmtals47umqaHXwmfvZfFZ/FzSpM3vIg/vCrAkruhyTgSDmtF/LJHRIwJ4mKPiBgTxMUeETEmGL3O3tF5vK7cJFOZJ5zM5TXKi/ngMy79E+v6aWf7MNzoxB/u0zK3yLzmNV1OWVVlL7k5p1PTb2jFpRlq1Jf12s7OkiezX2FC9wtm5mzUW5ZSFAenC6ZoTpgUc2nJesldXlEzWmXb6sA14oBPSLctOn14dkbHmMlZAo96jcyUKZ2DpGXb7VK7zfXrpm7jupowmaBiZ8eSXLRpvyfl3NNyNOZAnP2J3zKist8zYnjdm3X4wY5wgyrNpsChffcc++c+BJlF/LJHRIwJ4mKPiBgTjFaMDwFJh5us7eSoYERmK96y6J4nU03OkVywJOMJMFh0Z141L1wx71nOcb7PLCx1y9U9FU0rZRtk0qjrcdNlBGXXqtlZG8TCprcGeaClXKoiDrhIud/rOpn6ysSTfnLJ0g1sEt/bffc/aOpmDXmFPou3PPw20+7amvLjX7xsvesalEKqRumTas7bMKEH4ANh9naUh4/F+OKE5cxj02SjatWmrOh7trdLnncpl+V3QJZVNoF5tY9foNCnvH9sFAVTxwFXLXr/epzpwiDz3c3NdPHLHhExJoiLPSJiTBAXe0TEmODYot68eY3JHX265RyZdTg6TpybKufvSruopgKTUdJ5ieOeTyjyrODzwDVVL2Lds+bMa4Zf3qWiTmivorpn9depGdW3zz38aLdc3tky7fJEIlHdcToquxOT1td2psjpKSWEfOQdj5m6h0g3v76uevPSCbvHsEl51VYp0g8A5sgceemN5W752hWr27P7sJ/viQmdu/IeuSATGQZgOeDF8cbvkom0Ti7OSeLfP859Z+dK6NjXMfkJE6aEHgo27T/t8vM1OSX0AOJLo5Z7FX2Iz3b8skdEjAniYo+IGBOM3IPuwGvMiygpkktS6f5iPJM6eI44Fsl9St4kpSYN5gH340hT+qe266POJjYW+3J2GnMpjqqzfTDXfb1miS2EOODXVjTlcbNqPehmKGVzdWfDXptCu04sqCjtiT62N9W81kM8kagIypF+l5xZa2t7q1tmcx0ATE+peM5z0GzWTLt0W1UScWatXFrnsV5Vrz7m+AOACVFzbNrJt+WyXi/hlNDO44z5JFqwzywMEJ/5feH31L9/Tbp2o27rvMmx37UGYRgu+vhlj4gYE8TFHhExJhjtbjxUJEq53eE07Zbn3A52mkTTFgWP5Fym1gKd1+rJBEueSSRGZV0AB4v16bSdngm6Xq2qdemJKduOgi9urLugDSpncjaQh9Mpba4rPbKLP0GBdp+3t2wQS0jImkDqxZkz1oOOA5FeeukFU7dFO/xNIsAoO7XjwoVXu2XeOQeABs3/DnkbZiYsr9/coqoo4iw029uqQmTo/Ziash50LDKXXSqufjvdPV5ywyYwcaJ10jpcLesNmOGy6yR1uLwuPV54hwfMDIv4ZY+IGBPExR4RMSaIiz0iYkwwctPbAbxOI2x68x5M5DXHaov3wsuSGQSOW7zOpBRkp8g43njW/zwpIXtgsddczaUQxrTq8DOUahgAmgXaV2ja8XPiS8ujb9vxtT0JSJrIK0yclSP6mJopdcvf/q7llN/YUg+1HM3p5rb1XLtyVYkqn3jfB0zdNrXlFMuJMzPxs61WrVlue1fNbWnaW5kpWp2dzYPtlp9TLdeJQ97v6dw2htWdmTbe7RdIH7aMXsLJYYkyDsew+dmXAewCaAFIQgiPi8g8gN8EcA7AMoCfCSFs9usjIiLieHErYvyHQgiPhRAe7xx/CsCzIYSHATzbOY6IiHiT4k7E+I8D+GCn/Fns54D75KATBCrCePMD87H51E3MKZ/LqgjXTqz4XCfPr6YT56pE6pAnvnbP3WVG5TjiCgU1vRENHGotm8V1Z0evnffmwQm9djZtCTZ2dlT0zZA3nTRtwEyqreJu06kQbRKLNze3uuXJ6R3T7qGH3t4tX7myYurWidgiRfYf9hYDgLe/4x3d8rzLBLtHqsbiKU0ntb1thb89MstNTVmzXIauvbuj46/WvechqT9ujPkJ4i8kj8uGS8uVkPo2KKtqL//74YQSg3jih4UP9DLEGd4u16kaJN4P+2UPAH5XRL4pIk93/rYUQjigKrkGYOnwUyMiIt4MGPbL/mQI4bKInATwZRH5PleGEIKI/6nZR+fH4en98h2NNSIi4g4w1Jc9hHC58/8agM9jP1XzqoicBoDO/2t9zn0mhPB4Z1PvaEYdERFxy7jpl11EJgGkQgi7nfKPA/gnAL4I4BMAfrHz/xeGumJnwXuCijy5W+YLVs9lTbpJhHxouWgt0sN6zGbET14k91bvtsumrEbD6uL802hMds6MUymrfrnn9P6paSVoyDo/WL72bk2v3XRRXkyKOeHSOacpKrBK49/ctjp7iUgan3zyI6bu4rLmdDt/QcvZrN0HKVJK67qfb6MT6498NmOf7cnTZ7tln+ut0dIJb9Hke759jqSTxqA03pxe2T73HLku+/77RqU5WOHWf9jYbDbsR8+ZKfvsDwBAOHC5bfVtMpQYvwTg852vcgbAfwgh/DcR+QaA3xKRpwC8AeBnhugrIiLimHDTxR5CuADgXYf8fR3AR3rPiIiIeDNi9OmfDsR4J8JOFFW0nnC84EKidovMbe3Eio5sTmk5s1wmw9x1+nfvgcbRcTkXlcZRdhnDaWdFQiaKaCVWJKxSVFYmaznXCnTfbeo/OHVlu6x9+Ig75rqfofRMFZc6emVVOd8nXcRalVSeFpn9RKypcIM46DL5oqnb3dPrra5pBB/cfHMK5x3nobdBEX1ZikDMOvNaIa9z4Ik4rImNzbuOg44kZE/0wSawHuGc0zVxPoIB/HGDTHvmuu5qw2x5hQGMF9E3PiJiTBAXe0TEmCAu9oiIMcFodXaRLlFjxpFFMlONOLPcBLm3Gk72CasnZkh3Dm2rX3LutMSQAVodh9Mchx6dqV8+OkeeSc1yeav3Z+k+6xUb5bWXqOtoOsXmKtMMeeYxz1h9m3nMOU+b93Hgedxy+dfWKHUyz0G5YgknJynabK9iGWIqFTUX1sh0eOLkGdOOOfDDru3/1FnNQXed+OaDy4K2V9Hxp1N2sng/xZrUvG4r1M6ZvGjufK63fma03lxvXL71SDk/jv7nRJ09ImLsERd7RMSYYOSmtwOxx3vQsZzTcql5DN83oe1EKCaPTJxZrl3XYya9EKdOFApq/tp1aZ2QUxF/ilIV7bj0TEyA0Wp70xtHrNn7YimNPbwaboxZMjUl3tRE3nvseTdVstzz5evq3cxpsAErFtcp5bTnJm+3dYxrZMoDgPXretyi+7y8smza1Wta58kzp6ZL3fI0efzt7VgSzyw9z1rNqiQsC7NIH3rE8UHEEOx55yLR+qRr8qL60Oa2AaI6192O63n8skdEjAniYo+IGBOMXIw/8E7iNE6ATeXkJRTmj8vS1rRTBIz45UV/7rNNaaJ8iqcUeWoF54XHGV5ZjMo4jy4WEZsNK2Zzps+My+bJvGgs9hWL1ksub7zObPqnCeJnSxPpx/raFdOOd9bzeeuxmCZvQ55vDiYCgNVr2qdXmxoNSrtEc1xw/HEN6jNxgUfra6oKsIWj3e4vwvakBKNrJwnxHDqvR96pHySC94jP1LQ9KE+U8a5zVfwH3k1P+Wb9x3FwPEi4j1/2iIgxQVzsERFjgrjYIyLGBCPV2VOSQrGTj80TMbLHWK8+xaY4Mj/4lFlU5/uvUrph9t7zJBe5FEezWQ2oSqasbFrHmHXtMlTXcINMG48uO/4M7RdMTlo9nXHlsuqymZzd+yiVVEetUtTe3q6NKGO+jVrO6sp5ikjM0b7C1KQ10UG0Xb3hvQh1/ndpjsu7NvqO9dWefAFpndf166vdcolSVgNAg8yqPt13oH0L1nkTt1czyGzWrx1gdWd+X26J193kcONrDb52v3H0Q/yyR0SMCeJij4gYE4w4EAZo9xE3miRWpXsCUPg3iTjFXIonE+bgZGQvknfP8bI0XTvtUkOlWmw2U/G54ExjFSaoyFg1oUkpnzKOwMMHcRzAkzowQQGbEffHrH0w13rNmbWmSE2YWzxh6opFDU5pUcBMccqSbQwyV127poQVzQ01D3ovPOPV5h5RitShJnHy7dB97Y9LzXmVsuXr43enRXPl34dhzFqHwXq1mRrXv5b9OxckHNquJ2Uzn+aH1LnNQdpD/LJHRIwJ4mKPiBgTxMUeETEmGD3hZEdH6+9M2BspxuYq5o33pIF5MkM1nftmLq8RcSatr8/1FvoTPrRZV6YdgkLBkmhkyZ01VOydsmstu6XuX5uIIijirunMRPm83qcn5+Rb2yGu+IxjwMgRIUjapXNmjsxMTtu1nJsqu/fuOZNajcgruJ1PU816eTZnx8i6LZOONlx+u2xT58MTmtTJbTdNz9MTjWbMs+jP+T4I5nXxXrXmlbbPvc1k7xxF57eTUgPGMYStL37ZIyLGBHGxR0SMCUYqxoegIrRPmVsgAgXxpgnyghLKldzD7y0cOde/LkeiacaZ11hczLq6JouElDo65aLeciTG51ydEUcdpzyPOSF53KeVZvF2aqZk6m6sqqdZwiQgbq4qVRWzPdc4E3pMT6mJLpuz6gSrCTvbW6auTB57hkffXYuJSjJOjB/WO43ncWLScfFPLui1ONqxYU10oUnHTsRPDTCp9ZpuD8bo3TvJtOeq+D6N+dE1NGJ9ytcd5EI/dDj7p/SvMoMpich/EpHvi8jLIvI+EZkXkS+LyKud/+du3lNERMRxYVgx/lcA/LcQwtuwnwrqZQCfAvBsCOFhAM92jiMiIt6kGCaL6yyAPwHgrwBACKEBoCEiHwfwwU6zzwL4KoBPDu4tdAMQ0ml36RSL2TaIRUg2YXICL2azmOZ3dlkEMrv4LviCAyRazjuNd5WrxNPmVYEc8djl81ZcrNZIffHiKO0ysxiYSlvRkeen4TjXOL0UXyBp2Hup0fiDo05u030W6V7qTvRdJx47H+DCHozMN+jJJZiHr1m3qh3v1DM81bgQJ196+pSpm5ha1DHRnDZr1guvUdZ7SfYsx50R+Xu2yMMhpcEQJ5739e7sIdHoP4xhKOmG+bI/AOA6gH8jIt8WkX/dSd28FEI4CL+6hv1srxEREW9SDLPYMwDeA+BXQwjvBlCGE9nD/k/moT9sIvK0iDwvIs/328yIiIi4+xhmsa8AWAkhPNc5/k/YX/yrInIaADr/rx12cgjhmRDC4yGEx32cekRExOgwTH72ayJySUTeGkL4AfZzsr/U+fcJAL/Y+f8Lw1xQ5PAF3yKPt5qrYx0nF4hcwun9xgtKrF7XIgKFNI3B7x2kSU/c3bG88Wwi4WgwT4TAFrWU805rBdKVnSzEkg+rcZ6QgaPltihtcqfXQ8frTUGss9drNiKubTzjdA58Cmsm/vCpqU10Iu2RyAAvMK+jMruHiS5LO7LSCTUE5adPm7rJOdXhOVVWo+Y8/nbVRFfLXTJ1jR0l1mxVttwQD89pMFCF9mbhPlUyIDLPy9HD8NIPa2f/WwB+XfYTdF8A8FexLxX8log8BeANAD8zZF8RERHHgKEWewjhOwAeP6TqI0c6moiIiLuGkQfCdG0GPeYGJmSwQSws5wQijeA0ToDLqOkdmEisZ4KH4MxrDeIW96Y35khjT76Wk2BzlIaqkbFmROaZSxz5Bm9psNdcxnHsGy41FxQyLDjYqNdjUcusWvhUU9ZLcZAY2T+4w3jJDSCNYM9D5Gzm2szEdLecL1rfrolpJebIkhmx0bDtMoVSt5zO2WAa9pasppZNXdjTjLfe844hA+aHPRhZy00NIMDw5tJh7H5xxywiYkwQF3tExJggLvaIiDHBiKPeAtodpc2rGMZl0JnnWJdjy01oeR1Jbyebt3pukwgX2y3Sy1O2jybld2s6fTgl2n8ur7q4s4whSWi8zrRn0wZ7DnJqR+d5t9EGuZV6kkazvzHIHNPfijM0hk1DPAhsXhLnh2HMUEz6UbCRbZmC6uxpr88X1JU2l1ddPJ217YKoWdGbh1OUSyC4Ca+S6a1VJjfbHnYWfjD9nctsgJ2fj3afhsM9i/hlj4gYE8TFHhExJpCjEMWGvpjIdew74CwCuHGT5ncbb4YxAHEcHnEcFrc6jvtDCCcOqxjpYu9eVOT5EMJhTjpjNYY4jjiOUY4jivEREWOCuNgjIsYEx7XYnzmm6zLeDGMA4jg84jgsjmwcx6KzR0REjB5RjI+IGBOMdLGLyMdE5Acicl5ERsZGKyK/JiJrIvIC/W3kVNgicq+IfEVEXhKRF0Xk549jLCJSEJGvi8h3O+P4x52/PyAiz3Wez292+AvuOkQk3eE3/NJxjUNElkXkD0TkOyLyfOdvx/GO3DXa9pEtdtnP0vAvAfwpAI8A+FkReWREl/+3AD7m/nYcVNgJgF8IITwC4AkAP9eZg1GPpQ7gwyGEdwF4DMDHROQJAL8E4JdDCA8B2ATw1F0exwF+Hvv05Ac4rnF8KITwGJm6juMduXu07SGEkfwD8D4A/52OPw3g0yO8/jkAL9DxDwCc7pRPA/jBqMZCY/gCgI8e51gAFAF8C8CPYt95I3PY87qL1z/beYE/DOBL2Pf6Po5xLANYdH8b6XMBMAvgdXT20o56HKMU4+8BwOReK52/HReOlQpbRM4BeDeA545jLB3R+TvYJwr9MoDXAGyF0GXUGNXz+RcA/h7QTYu7cEzjCAB+V0S+KSJPd/426udyV2nb4wYdBlNh3w2IyBSA3wbwt0MIJlvBqMYSQmiFEB7D/pf1vQDedrev6SEiPwlgLYTwzVFf+xA8GUJ4D/bVzJ8TkT/BlSN6LndE234zjHKxXwZwLx2f7fztuDAUFfZRQ0Sy2F/ovx5C+M/HORYACCFsAfgK9sXlkkg3jncUz+cDAH5KRJYBfA77ovyvHMM4EEK43Pl/DcDnsf8DOOrncke07TfDKBf7NwA83NlpzQH48wC+OMLre3wR+xTYwC1QYd8JZJ9U7TMAXg4h/PPjGouInBCRUqc8gf19g5exv+h/elTjCCF8OoRwNoRwDvvvw/8MIfzFUY9DRCZFZPqgDODHAbyAET+XEMI1AJdE5K2dPx3Qth/NOO72xofbaPgJAK9gXz/8ByO87m8AuAqgif1fz6ewrxs+C+BVAP8DwPwIxvEk9kWw7wH4TuffT4x6LAB+GMC3O+N4AcA/7Pz9LQC+DuA8gP8IID/CZ/RBAF86jnF0rvfdzr8XD97NY3pHHgPwfOfZ/BcAc0c1juhBFxExJogbdBERY4K42CMixgRxsUdEjAniYo+IGBPExR4RMSaIiz0iYkwQF3tExJggLvaIiDHB/wdE016EWf06LgAAAABJRU5ErkJggg==\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "index = 25\n",
    "plt.imshow(train_set_x_orig[index])  #展示图片，index为序号"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "db894331",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "y=[1], it's a cat' picture\n"
     ]
    }
   ],
   "source": [
    "#打印出当前的训练标签值\n",
    "#使用np.squeeze的目的是压缩维度，【未压缩】train_set_y[:,index]的值为[1] , 【压缩后】np.squeeze(train_set_y[:,index])的值为1\n",
    "#print(\"【使用np.squeeze：\" + str(np.squeeze(train_set_y[:,index])) + \"，不使用np.squeeze： \" + str(train_set_y[:,index]) + \"】\")\n",
    "#只有压缩后的值才能进行解码操作\n",
    "print(\"y=\" + str(train_set_y[:,index]) + \", it's a \" + classes[np.squeeze(train_set_y[:,index])].decode(\"utf-8\") + \"' picture\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "7094557a",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "训练集的数量: m_train = 209\n",
      "测试集的数量 : m_test = 50\n",
      "每张图片的宽/高 : num_px = 64\n",
      "每张图片的大小 : (64, 64, 3)\n",
      "训练集_图片的维数 : (209, 64, 64, 3)\n",
      "训练集_标签的维数 : (1, 209)\n",
      "测试集_图片的维数: (50, 64, 64, 3)\n",
      "测试集_标签的维数: (1, 50)\n"
     ]
    }
   ],
   "source": [
    "m_train = train_set_y.shape[1] #训练集里图片的数量。\n",
    "m_test = test_set_y.shape[1] #测试集里图片的数量。\n",
    "num_px = train_set_x_orig.shape[1] #训练、测试集里面的图片的宽度和高度（均为64x64）\n",
    "\n",
    "#现在看一看我们加载的东西的具体情况\n",
    "print (\"训练集的数量: m_train = \" + str(m_train))\n",
    "print (\"测试集的数量 : m_test = \" + str(m_test))\n",
    "print (\"每张图片的宽/高 : num_px = \" + str(num_px))\n",
    "print (\"每张图片的大小 : (\" + str(num_px) + \", \" + str(num_px) + \", 3)\") #3指的是三原色\n",
    "# 64*64个像素 每个元素的RGB为3 \n",
    "# 需要每列代表一个平坦的图像，需要把数组构造为（64 x 64 x 3，1）【降维】\n",
    "print (\"训练集_图片的维数 : \" + str(train_set_x_orig.shape))\n",
    "print (\"训练集_标签的维数 : \" + str(train_set_y.shape))\n",
    "print (\"测试集_图片的维数: \" + str(test_set_x_orig.shape))\n",
    "print (\"测试集_标签的维数: \" + str(test_set_y.shape))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "ae653d51",
   "metadata": {},
   "outputs": [],
   "source": [
    "#【降维flatten】\n",
    "#将训练集的维度降低并转置。\n",
    "train_set_x_flatten  = train_set_x_orig.reshape(train_set_x_orig.shape[0],-1).T\n",
    "#将测试集的维度降低并转置。\n",
    "test_set_x_flatten = test_set_x_orig.reshape(test_set_x_orig.shape[0], -1).T"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "e2f3eebd",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "训练集降维最后的维度： (12288, 209)\n",
      "训练集_标签的维数 : (1, 209)\n",
      "测试集降维之后的维度: (12288, 50)\n",
      "测试集_标签的维数 : (1, 50)\n"
     ]
    }
   ],
   "source": [
    "print (\"训练集降维最后的维度： \" + str(train_set_x_flatten.shape))\n",
    "print (\"训练集_标签的维数 : \" + str(train_set_y.shape))\n",
    "print (\"测试集降维之后的维度: \" + str(test_set_x_flatten.shape))\n",
    "print (\"测试集_标签的维数 : \" + str(test_set_y.shape))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "f09219ab",
   "metadata": {},
   "outputs": [],
   "source": [
    "#标准化训练集\n",
    "train_set_x = train_set_x_flatten\n",
    "test_set_x = test_set_x_flatten"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "d4408540",
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "\"\"\"\n",
    "建立神经网络的主要步骤是：\n",
    "定义模型结构（例如输入特征的数量）\n",
    "初始化模型的参数\n",
    "循环：\n",
    "3.1 计算当前损失（正向传播）\n",
    "3.2 计算当前梯度（反向传播）\n",
    "3.3 更新参数（梯度下降）\n",
    "\"\"\"\n",
    "#构造sigmoid函数\n",
    "def sigmoid(z):\n",
    "    #参数： z  - 任何大小的标量或numpy数组。\n",
    "    s = .5 * (1 + np.tanh(.5 * z))\n",
    "    return s"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "2684c946",
   "metadata": {},
   "outputs": [],
   "source": [
    "# w,b的初始化，都初始化为0\n",
    "def initialize_with_zeros(dim):\n",
    "    \"\"\"\n",
    "        此函数为w创建一个维度为（dim，1）的0向量，并将b初始化为0。\n",
    "        \n",
    "        参数：\n",
    "            dim  - 我们想要的w矢量的大小（或者这种情况下的参数数量）\n",
    "        \n",
    "        返回：\n",
    "            w  - 维度为（dim，1）的初始化向量。\n",
    "            b  - 初始化的标量（对应于偏差）\n",
    "    \"\"\"\n",
    "    w = np.zeros(shape = (dim,1))\n",
    "    b = 0\n",
    "    #使用断言来确保我要的数据是正确的\n",
    "    assert(w.shape == (dim, 1)) #w的维度是(dim,1)\n",
    "    assert(isinstance(b, float) or isinstance(b, int)) #b的类型是float或者是int\n",
    "    return(w , b)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "fd5b6060",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 成本函数实现\n",
    "def propagate(w, b, X, Y):\n",
    "    \"\"\"\n",
    "    实现前向和后向传播的成本函数及其梯度。\n",
    "    参数：\n",
    "        w  - 权重，大小不等的数组（num_px * num_px * 3，1）\n",
    "        b  - 偏差，一个标量\n",
    "        X  - 矩阵类型为（num_px * num_px * 3，训练数量）\n",
    "        Y  - 真正的“标签”矢量（如果非猫则为0，如果是猫则为1），矩阵维度为(1,训练数据数量)\n",
    "\n",
    "    返回：\n",
    "        cost- 逻辑回归的负对数似然成本\n",
    "        dw  - 相对于w的损失梯度，因此与w相同的形状\n",
    "        db  - 相对于b的损失梯度，因此与b的形状相同\n",
    "    \"\"\"\n",
    "    m = X.shape[1]\n",
    "    \n",
    "    #正向传播\n",
    "    A = sigmoid(np.dot(w.T,X) + b) #计算激活值，请参考公式2。\n",
    "    cost = (- 1 / m) * np.sum(Y * np.log(A + 1e-5) + (1 - Y) * (np.log(1 - A + 1e-5))) #计算成本，请参考公式3和4。\n",
    "    \n",
    "    #反向传播\n",
    "    dw = (1 / m) * np.dot(X, (A - Y).T) #请参考视频中的偏导公式。\n",
    "    db = (1 / m) * np.sum(A - Y) #请参考视频中的偏导公式。\n",
    "    \n",
    "    #使用断言确保我的数据是正确的\n",
    "    assert(dw.shape == w.shape)\n",
    "    assert(db.dtype == float)\n",
    "    cost = np.squeeze(cost)\n",
    "    assert(cost.shape == ())\n",
    "    \n",
    "    #创建一个字典，把dw和db保存起来。\n",
    "    grads = {\n",
    "                \"dw\": dw,\n",
    "                \"db\": db\n",
    "             }\n",
    "    return (grads , cost)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "58b8760a",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "dw = [[0.99993216]\n",
      " [1.99980262]]\n",
      "db = 0.49993523062470574\n",
      "cost = 6.000064773192205\n",
      "[[1]\n",
      " [2]]\n",
      "2\n",
      "[[1 2]\n",
      " [3 4]]\n",
      "[[1 0]]\n"
     ]
    }
   ],
   "source": [
    "w, b, X, Y = np.array([[1], [2]]), 2, np.array([[1,2], [3,4]]), np.array([[1, 0]])\n",
    "grads, cost = propagate(w, b, X, Y)\n",
    "print (\"dw = \" + str(grads[\"dw\"]))\n",
    "print (\"db = \" + str(grads[\"db\"]))\n",
    "print (\"cost = \" + str(cost))\n",
    "print(w)\n",
    "print(b)\n",
    "print(X)\n",
    "print(Y)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "e6bcbebc",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 梯度下降更新参数\n",
    "def optimize(w , b , X , Y , num_iterations , learning_rate , print_cost = False):\n",
    "    \"\"\"\n",
    "    此函数通过运行梯度下降算法来优化w和b\n",
    "    \n",
    "    参数：\n",
    "        w  - 权重，大小不等的数组（num_px * num_px * 3，1）\n",
    "        b  - 偏差，一个标量\n",
    "        X  - 维度为（num_px * num_px * 3，训练数据的数量）的数组。\n",
    "        Y  - 真正的“标签”矢量（如果非猫则为0，如果是猫则为1），矩阵维度为(1,训练数据的数量)\n",
    "        num_iterations  - 优化循环的迭代次数\n",
    "        learning_rate  - 梯度下降更新规则的学习率\n",
    "        print_cost  - 每100步打印一次损失值\n",
    "    \n",
    "    返回：\n",
    "        params  - 包含权重w和偏差b的字典\n",
    "        grads  - 包含权重和偏差相对于成本函数的梯度的字典\n",
    "        成本 - 优化期间计算的所有成本列表，将用于绘制学习曲线。\n",
    "    \n",
    "    提示：\n",
    "    我们需要写下两个步骤并遍历它们：\n",
    "        1）计算当前参数的成本和梯度，使用propagate（）。\n",
    "        2）使用w和b的梯度下降法则更新参数。\n",
    "    \"\"\"\n",
    "    \n",
    "    costs = []\n",
    "    \n",
    "    for i in range(num_iterations):\n",
    "        \n",
    "        grads, cost = propagate(w, b, X, Y)\n",
    "        \n",
    "        dw = grads[\"dw\"]\n",
    "        db = grads[\"db\"]\n",
    "        \n",
    "        w = w - learning_rate * dw\n",
    "        b = b - learning_rate * db\n",
    "        \n",
    "        #记录成本\n",
    "        if i % 100 == 0:\n",
    "            costs.append(cost)\n",
    "        #打印成本数据\n",
    "        if (print_cost) and (i % 100 == 0):\n",
    "            print(\"迭代的次数: %i ， 误差值： %f\" % (i,cost))\n",
    "        \n",
    "    params  = {\n",
    "                \"w\" : w,\n",
    "                \"b\" : b }\n",
    "    grads = {\n",
    "            \"dw\": dw,\n",
    "            \"db\": db } \n",
    "    return (params , grads , costs)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "3df4f96a",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "====================测试optimize====================\n",
      "w = [[0.1124579 ]\n",
      " [0.23106775]]\n",
      "b = 1.5593049248448891\n",
      "dw = [[0.90158428]\n",
      " [1.76250842]]\n",
      "db = 0.4304620716786828\n"
     ]
    }
   ],
   "source": [
    "#测试optimize\n",
    "print(\"====================测试optimize====================\")\n",
    "w, b, X, Y = np.array([[1], [2]]), 2, np.array([[1,2], [3,4]]), np.array([[1, 0]])\n",
    "params , grads , costs = optimize(w , b , X , Y , num_iterations=100 , learning_rate = 0.009 , print_cost = False)\n",
    "print (\"w = \" + str(params[\"w\"]))\n",
    "print (\"b = \" + str(params[\"b\"]))\n",
    "print (\"dw = \" + str(grads[\"dw\"]))\n",
    "print (\"db = \" + str(grads[\"db\"]))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "506fc112",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "====================测试predict====================\n",
      "predictions = [[1. 1.]]\n"
     ]
    }
   ],
   "source": [
    "def predict(w , b , X ):\n",
    "    \"\"\"\n",
    "    使用学习逻辑回归参数logistic （w，b）预测标签是0还是1，\n",
    "    \n",
    "    参数：\n",
    "        w  - 权重，大小不等的数组（num_px * num_px * 3，1）\n",
    "        b  - 偏差，一个标量\n",
    "        X  - 维度为（num_px * num_px * 3，训练数据的数量）的数据\n",
    "    \n",
    "    返回：\n",
    "        Y_prediction  - 包含X中所有图片的所有预测【0 | 1】的一个numpy数组（向量）\n",
    "    \n",
    "    \"\"\"\n",
    "    \n",
    "    m  = X.shape[1] #图片的数量\n",
    "    Y_prediction = np.zeros((1,m)) \n",
    "    w = w.reshape(X.shape[0],1)\n",
    "    \n",
    "    #计预测猫在图片中出现的概率\n",
    "    A = sigmoid(np.dot(w.T , X) + b)\n",
    "    for i in range(A.shape[1]):\n",
    "        #将概率a [0，i]转换为实际预测p [0，i]\n",
    "        Y_prediction[0,i] = 1 if A[0,i] > 0.5 else 0\n",
    "    #使用断言\n",
    "    assert(Y_prediction.shape == (1,m))\n",
    "    \n",
    "    return Y_prediction\n",
    "#测试predict\n",
    "print(\"====================测试predict====================\")\n",
    "w, b, X, Y = np.array([[1], [2]]), 2, np.array([[1,2], [3,4]]), np.array([[1, 0]])\n",
    "print(\"predictions = \" + str(predict(w, b, X)))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "3a9eff68",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "====================测试model====================\n",
      "迭代的次数: 0 ， 误差值： 0.693127\n",
      "迭代的次数: 100 ， 误差值： 6.224687\n",
      "迭代的次数: 200 ， 误差值： 3.966169\n",
      "迭代的次数: 300 ， 误差值： 3.966169\n",
      "迭代的次数: 400 ， 误差值： 5.067885\n",
      "迭代的次数: 500 ， 误差值： 0.771191\n",
      "迭代的次数: 600 ， 误差值： 0.330505\n",
      "迭代的次数: 700 ， 误差值： -0.000010\n",
      "迭代的次数: 800 ， 误差值： -0.000010\n",
      "迭代的次数: 900 ， 误差值： -0.000010\n",
      "迭代的次数: 1000 ， 误差值： -0.000010\n",
      "迭代的次数: 1100 ， 误差值： -0.000010\n",
      "迭代的次数: 1200 ， 误差值： -0.000010\n",
      "迭代的次数: 1300 ， 误差值： -0.000010\n",
      "迭代的次数: 1400 ， 误差值： -0.000010\n",
      "迭代的次数: 1500 ， 误差值： -0.000010\n",
      "迭代的次数: 1600 ， 误差值： -0.000010\n",
      "迭代的次数: 1700 ， 误差值： -0.000010\n",
      "迭代的次数: 1800 ， 误差值： -0.000010\n",
      "迭代的次数: 1900 ， 误差值： -0.000010\n",
      "训练集准确性： 100.0 %\n",
      "测试集准确性： 72.0 %\n"
     ]
    }
   ],
   "source": [
    "def model(X_train , Y_train , X_test , Y_test , num_iterations = 2000 , learning_rate = 0.5 , print_cost = False):\n",
    "    \"\"\"\n",
    "    通过调用之前实现的函数来构建逻辑回归模型\n",
    "    \n",
    "    参数：\n",
    "        X_train  - numpy的数组,维度为（num_px * num_px * 3，m_train）的训练集\n",
    "        Y_train  - numpy的数组,维度为（1，m_train）（矢量）的训练标签集\n",
    "        X_test   - numpy的数组,维度为（num_px * num_px * 3，m_test）的测试集\n",
    "        Y_test   - numpy的数组,维度为（1，m_test）的（向量）的测试标签集\n",
    "        num_iterations  - 表示用于优化参数的迭代次数的超参数\n",
    "        learning_rate  - 表示optimize（）更新规则中使用的学习速率的超参数\n",
    "        print_cost  - 设置为true以每100次迭代打印成本\n",
    "    \n",
    "    返回：\n",
    "        d  - 包含有关模型信息的字典。\n",
    "    \"\"\"\n",
    "    w , b = initialize_with_zeros(X_train.shape[0])\n",
    "    \n",
    "    parameters , grads , costs = optimize(w , b , X_train , Y_train,num_iterations , learning_rate , print_cost)\n",
    "    \n",
    "    #从字典“参数”中检索参数w和b\n",
    "    w , b = parameters[\"w\"] , parameters[\"b\"]\n",
    "    \n",
    "    #预测测试/训练集的例子\n",
    "    Y_prediction_test = predict(w , b, X_test)\n",
    "    Y_prediction_train = predict(w , b, X_train)\n",
    "    \n",
    "    #打印训练后的准确性\n",
    "    print(\"训练集准确性：\"  , format(100 - np.mean(np.abs(Y_prediction_train - Y_train)) * 100) ,\"%\")\n",
    "    print(\"测试集准确性：\"  , format(100 - np.mean(np.abs(Y_prediction_test - Y_test)) * 100) ,\"%\")\n",
    "    \n",
    "    d = {\n",
    "            \"costs\" : costs,\n",
    "            \"Y_prediction_test\" : Y_prediction_test,\n",
    "            \"Y_prediciton_train\" : Y_prediction_train,\n",
    "            \"w\" : w,\n",
    "            \"b\" : b,\n",
    "            \"learning_rate\" : learning_rate,\n",
    "            \"num_iterations\" : num_iterations }\n",
    "    return d\n",
    "\n",
    "print(\"====================测试model====================\")     \n",
    "#这里加载的是真实的数据，请参见上面的代码部分。\n",
    "d = model(train_set_x, train_set_y, test_set_x, test_set_y, num_iterations = 2000, learning_rate = 0.005, print_cost = True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7e839a2b",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.8"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}